/*jslint bitwise: true, node: true */
'use strict';

var express = require('express');
var app = express();
var wss = require('@small-tech/express-ws')(app);
var SAT = require('sat');
var get = require('simple-get');
var bodyParser = require('body-parser');
var msgpack = require('@msgpack/msgpack');
var base64id = require('base64id');

// Import game settings.
var yaml = require('js-yaml');
var fs = require('fs');
var c = yaml.load(fs.readFileSync('../config.yaml'));

// Import utilities.
var util = require('./lib/util');

// Import quadtree.
var quadtree = require('simple-quadtree');

var tree = quadtree(0, 0, c.game.width, c.game.height);

var users = [];
var massFood = [];
var food = [];
var virus = [];
var mothercell = [];
var mothercellFood = [];
var foodStructSpiral = [];
var foodStructSpiralFood = [];
var foodStructDotCircle = [];
var foodStructDotCircleFood = [];
var sockets = {};

var leaderboard = [];
var leaderboardChanged = false;

var V = SAT.Vector;
var C = SAT.Circle;

var initMassLog = util.log(c.player.mass, c.magic.slow_base);

app.use(express.static(__dirname + '/../client'));

if(c.chat_connect.enable) {
    app.use(bodyParser.json());
    app.post("/chatconnect",function(req,res) {
	if(req.body.secret && req.body.secret == c.chat_connect.incoming.secret) {
	    res.send("Ok");
	    console.log("Chat message from bridge received.");
	    if(c.logging.chat) console.log('[CHAT] [' + (new Date()).getHours() + ':' + (new Date()).getMinutes() + '] ' + req.body.sender + ': ' + req.body.message);
	    wss.broadcast(msgpack.encode(['serverSendPlayerChat',{sender: req.body.sender, hue: req.body.hue, message: req.body.message}]));
	}
	else res.send("Access denied");
    });
}

function bridgeChat(data) {
    if(c.chat_connect.enable) {
	data.secret = c.chat_connect.outgoing.secret;
        get({
            url: "http://"+c.chat_connect.outgoing.host+":"+c.chat_connect.outgoing.port+"/"+c.chat_connect.outgoing.path,
            method: "POST",
            body: data,
            json:true
        },function(err,res) {
            if(err) console.error(err);
            if(c.logging.chat_connect) console.log("Chat Push Status: "+res.statusCode);
        });
    }
}

function addFood(toAdd) {
    while (toAdd--) {
	var mass = Array.isArray(c.food.mass) ? util.randomInRange(c.food.mass[0],c.food.mass[1]) : c.food.mass;
	var radius = util.massToRadius(mass);
        var position = c.food.uniform_disposition ? util.uniformPosition(food,radius,{width:c.game.width,height:c.game.height}) : util.randomPosition(radius,{width:c.game.width,height:c.game.height});
        food.push({
            // Make IDs unique.
            id: ((new Date()).getTime() + '' + food.length) >>> 0,
            x: position.x,
            y: position.y,
            radius: radius,
            mass: mass,
            hue: Math.round(Math.random() * 360)
        });
    }
}

function addVirus(toAdd) {
    while (toAdd--) {
        var mass = Array.isArray(c.virus.mass) ? util.randomInRange(c.virus.mass[0],c.virus.mass[1]) : c.virus.mass;
        var radius = util.massToRadius(mass);
        var position = c.virus.uniform_disposition ? util.uniformPosition(virus,radius,{width:c.game.width,height:c.game.height}) : util.randomPosition(radius,{width:c.game.width,height:c.game.height});
        virus.push({
            id: ((new Date()).getTime() + '' + virus.length) >>> 0,
            x: position.x,
            y: position.y,
            radius: radius,
            mass: mass,
        });
    }
}

function addMothercell(toAdd) {
    while (toAdd--) {
        var mass = Array.isArray(c.mothercell.mass) ? util.randomInRange(c.mothercell.mass[0],c.mothercell.mass[1]) : c.mothercell.mass;
        var radius = util.massToRadius(mass);
        var position = c.mothercellUniformDisposition ? util.uniformPosition(mothercell,radius,{width:c.game.width,height:c.game.height}) : util.randomPosition(radius,{width:c.game.width,height:c.game.height});
        mothercell.push({
            id: ((new Date()).getTime() + '' + mothercell.length) >>> 0,
            x: position.x,
            y: position.y,
            radius: radius,
            mass: mass,
            killMass: 0
        });
    }
}

function mothercellAddFood(mCell) {
    var currentCells = mothercellFood.map(function(thisFood) {
        return thisFood.for == this.id;
    },mCell).length;
    if(currentCells < c.mothercell.food_limit) {
	var radius = util.massToRadius(1);
	var distance = util.randomInRange(mCell.radius+25,mCell.radius+25+(c.mothercell.food_limit/5));
	var position = util.relativePosition({x:mCell.x,y:mCell.y},distance);
	mothercellFood.push({
    	    // Make IDs unique.
	    id: ((new Date()).getTime() + '' + mothercellFood.length) >>> 0,
    	    x: position.x,
    	    y: position.y,
    	    radius: radius,
    	    mass: 1,
    	    hue: Math.round(Math.random() * 360),
    	    for: mCell.id
    	});
    }
}

function mothercellKillPlayer(mCell) {
    if(mCell.killMass > 0) {
	var radius = util.massToRadius(1);
	var distance = util.randomInRange(mCell.radius+25,mCell.radius+25+(c.mothercell.food_limit/5));
	var position = util.relativePosition({x:mCell.x,y:mCell.y},distance);
	mCell.killMass--;
	mothercellFood.push({
	    // Make IDs unique.
	    id: ((new Date()).getTime() + '' + mothercellFood.length) >>> 0,
	    x: position.x,
	    y: position.y,
	    radius: radius,
	    mass: 1,
	    hue: Math.round(Math.random() * 360),
	    for: mCell.id
	});
	if(mCell.killMass > 0) setTimeout(function() {mothercellKillPlayer(mCell);},100);
    }
}

function addFoodStructSpiral(toAdd) {
    var radius = util.massToRadius(c.food_structure.spiral.mass.center);
    var foodRadius = util.massToRadius(c.food_structure.spiral.mass.border);
    while (toAdd--) {
        var position = c.food_structure.spiral.uniform_disposition ? util.uniformPosition(foodStructSpiral,radius,{width:c.game.width,height:c.game.height}) : util.randomPosition(radius,{width:c.game.width,height:c.game.height});
        var hue = Math.round(Math.random() * 360);
        foodStructSpiral.push({
            // Make IDs unique.
            id: ((new Date()).getTime() + '' + foodStructSpiral.length) >>> 0,
            x: position.x,
            y: position.y,
            radius: radius,
            mass: c.food_structure.spiral.mass.center,
            hue: hue
        });
        var addSpiralDots = 20;
        for(var i=0;i<addSpiralDots/2;i++) {
	    var aFoodPosition = util.relativePosition(position,radius+15+(i*3),foodRadius*1.7*i-90);
	    foodStructSpiralFood.push({
        	// Make IDs unique.
        	id: ((new Date()).getTime() + '' + foodStructSpiralFood.length) >>> 0,
        	x: aFoodPosition.x,
        	y: aFoodPosition.y,
        	radius: foodRadius,
        	mass: c.food_structure.spiral.mass.border,
        	hue: hue+(i*3)
    	    });
        }
        for(i=0;i<addSpiralDots/2;i++) {
            var bFoodPosition = util.relativePosition(position,radius+15+(i*3),foodRadius*1.7*i+90);
            foodStructSpiralFood.push({
                // Make IDs unique.
                id: ((new Date()).getTime() + '' + foodStructSpiralFood.length) >>> 0,
                x: bFoodPosition.x,
                y: bFoodPosition.y,
                radius: foodRadius,
                mass: c.food_structure.spiral.mass.border,
                hue: hue-(i*3)
            });
        }
    }
}

function addFoodStructDotCircle(toAdd) {
    var radius = util.massToRadius(c.food_structure.dot_circle.mass.center);
    var dotRadius = util.massToRadius(c.food_structure.dot_circle.mass.border_dot);
    var foodRadius = util.massToRadius(c.food_structure.dot_circle.mass.border);
    while (toAdd--) {
        var position = c.food_structure.dot_circle.uniform_disposition ? util.uniformPosition(foodStructDotCircle,radius,{width:c.game.width,height:c.game.height}) : util.randomPosition(radius,{width:c.game.width,height:c.game.height});
        var dotHue = Math.round(Math.random() * 360);
        var foodHue = Math.round(Math.random() * 360);
        foodStructDotCircle.push({
            // Make IDs unique.
            id: ((new Date()).getTime() + '' + foodStructDotCircle.length) >>> 0,
            x: position.x,
            y: position.y,
            radius: radius,
            mass: c.food_structure.dot_circle.mass.center,
            hue: Math.round(Math.random() * 360)
        });
        var addBorderDots = 25;
        var addBorderFood = 5;
        for(var i=0;i<addBorderDots;i++) {
	    var dotPosition = util.relativePosition(position,radius+15,(360/addBorderDots*i)-90);
	    foodStructDotCircleFood.push({
    		// Make IDs unique.
    		id: ((new Date()).getTime() + '' + foodStructDotCircleFood.length) >>> 0,
    		x: dotPosition.x,
    		y: dotPosition.y,
    		radius: dotRadius,
    		mass: c.food_structure.dot_circle.mass.border_dot,
    		hue: dotHue
	    });
        }
        for(i=0;i<addBorderFood;i++) {
            var foodPosition = util.relativePosition(position,radius+15,(360/addBorderFood*i)-90);
            foodStructDotCircleFood.push({
                // Make IDs unique.
                id: ((new Date()).getTime() + '' + foodStructDotCircleFood.length) >>> 0,
                x: foodPosition.x,
                y: foodPosition.y,
                radius: foodRadius,
                mass: c.food_structure.dot_circle.mass.border,
                hue: foodHue
            });
        }
    }
}

function removeFood(toRem) {
    while (toRem--) {
        food.pop();
    }
}

function movePlayer(player) {
    var x =0,y =0;
    for(var i=0; i<player.cells.length; i++)
    {
        var target = {
            x: player.x - player.cells[i].x + player.target.x,
            y: player.y - player.cells[i].y + player.target.y
        };
        var dist = Math.sqrt(Math.pow(target.y, 2) + Math.pow(target.x, 2));
        var deg = Math.atan2(target.y, target.x);
        var slowDown = 1;
        if(player.cells[i].speed <= 6.25) {
            slowDown = util.log(player.cells[i].mass, c.magic.slow_base) - initMassLog + 1;
        }

        var deltaY = player.cells[i].speed * Math.sin(deg)/ slowDown;
        var deltaX = player.cells[i].speed * Math.cos(deg)/ slowDown;

        if(player.cells[i].speed > 6.25) {
            player.cells[i].speed -= 0.5;
        }
        if (dist < (50 + player.cells[i].radius)) {
            deltaY *= dist / (50 + player.cells[i].radius);
            deltaX *= dist / (50 + player.cells[i].radius);
        }
        if (!isNaN(deltaY)) {
            player.cells[i].y += deltaY;
        }
        if (!isNaN(deltaX)) {
            player.cells[i].x += deltaX;
        }
        // Find best solution.
        for(var j=0; j<player.cells.length; j++) {
            if(j != i && player.cells[i] !== undefined) {
                var distance = Math.sqrt(Math.pow(player.cells[j].y-player.cells[i].y,2) + Math.pow(player.cells[j].x-player.cells[i].x,2));
                var radiusTotal = (player.cells[i].radius + player.cells[j].radius);
                if(distance < radiusTotal) {
                    if(player.lastSplit > new Date().getTime() - 1000 * c.game.merge_timer) {
                        if(player.cells[i].x < player.cells[j].x) {
                            player.cells[i].x--;
                        } else if(player.cells[i].x > player.cells[j].x) {
                            player.cells[i].x++;
                        }
                        if(player.cells[i].y < player.cells[j].y) {
                            player.cells[i].y--;
                        } else if((player.cells[i].y > player.cells[j].y)) {
                            player.cells[i].y++;
                        }
                    }
                    else if(distance < radiusTotal / 1.75) {
                        player.cells[i].mass += player.cells[j].mass;
                        player.cells[i].radius = util.massToRadius(player.cells[i].mass);
                        player.cells.splice(j, 1);
                    }
                }
            }
        }
        if(player.cells.length > i) {
            var borderCalc = player.cells[i].radius / 3;
            if (player.cells[i].x > c.game.width - borderCalc) {
                player.cells[i].x = c.game.width - borderCalc;
            }
            if (player.cells[i].y > c.game.height - borderCalc) {
                player.cells[i].y = c.game.height - borderCalc;
            }
            if (player.cells[i].x < borderCalc) {
                player.cells[i].x = borderCalc;
            }
            if (player.cells[i].y < borderCalc) {
                player.cells[i].y = borderCalc;
            }
            x += player.cells[i].x;
            y += player.cells[i].y;
        }
    }
    player.x = x/player.cells.length;
    player.y = y/player.cells.length;
}

function moveMass(mass) {
    var deg = Math.atan2(mass.target.y, mass.target.x);
    var deltaY = mass.speed * Math.sin(deg);
    var deltaX = mass.speed * Math.cos(deg);

    mass.speed -= 0.5;
    if(mass.speed < 0) {
        mass.speed = 0;
    }
    if (!isNaN(deltaY)) {
        mass.y += deltaY;
    }
    if (!isNaN(deltaX)) {
        mass.x += deltaX;
    }

    var borderCalc = mass.radius + 5;

    if (mass.x > c.game.width - borderCalc) {
        mass.x = c.game.width - borderCalc;
    }
    if (mass.y > c.game.height - borderCalc) {
        mass.y = c.game.height - borderCalc;
    }
    if (mass.x < borderCalc) {
        mass.x = borderCalc;
    }
    if (mass.y < borderCalc) {
        mass.y = borderCalc;
    }
}

function balanceMass() {
    var avgFoodMass = Array.isArray(c.food.mass) ? (c.food.mass[0]+c.food.mass[1])/2 : c.food.mass;
    var totalMass = food.length * avgFoodMass +
        users
            .map(function(u) {return u.massTotal; })
            .reduce(function(pu,cu) { return pu+cu;}, 0);

    var massDiff = c.game.mass - totalMass;
    var maxFoodDiff = c.food.limit - food.length;
    var foodDiff = parseInt(massDiff / avgFoodMass) - maxFoodDiff;
    var foodToAdd = Math.min(foodDiff, maxFoodDiff);
    var foodToRemove = -Math.max(foodDiff, maxFoodDiff);

    if (foodToAdd > 0) {
        //console.log('[DEBUG] Adding ' + foodToAdd + ' food to level!');
        addFood(foodToAdd);
        //console.log('[DEBUG] Mass rebalanced!');
    }
    else if (foodToRemove > 0) {
        //console.log('[DEBUG] Removing ' + foodToRemove + ' food from level!');
        removeFood(foodToRemove);
        //console.log('[DEBUG] Mass rebalanced!');
    }

    if(c.virus.enable) {
        var virusToAdd = c.virus.limit - virus.length;
        if (virusToAdd > 0) addVirus(virusToAdd);
    }
    if(c.mothercell.enable) {
        var mothercellToAdd = c.mothercell.limit - mothercell.length;
        if (mothercellToAdd > 0) addMothercell(mothercellToAdd);
    }
    if(c.food_structure.spiral.enable) {
	var foodStructSpiralToAdd = c.food_structure.spiral.limit - foodStructSpiral.length;
	if (foodStructSpiralToAdd > 0) addFoodStructSpiral(foodStructSpiralToAdd);
    }
    if(c.food_structure.dot_circle.enable) {
	var foodStructDotCircleToAdd = c.food_structure.dot_circle.limit - foodStructDotCircle.length;
	if (foodStructDotCircleToAdd > 0) addFoodStructDotCircle(foodStructDotCircleToAdd);
    }

}

app.ws('/socket',function(socket,request) {
    console.log('A user connected!',request.query.type);

    var type = request.query.type;
    var radius = util.massToRadius(c.player.mass);
    var position = c.player.uniform_disposition ? util.uniformPosition(users,radius,{width:c.game.width,height:c.game.height}) : util.randomPosition(radius,{width:c.game.width,height:c.game.height});

    var cells = [];
    var massTotal = 0;
    socket.id = base64id.generateId();
    if(type === 'player') {
        cells = [{
            mass: c.player.mass,
            x: position.x,
            y: position.y,
            radius: radius
        }];
        massTotal = c.player.mass;
    }
    var currentPlayer = {
        id: socket.id,
        x: position.x,
        y: position.y,
        w: c.player.mass,
        h: c.player.mass,
        cells: cells,
        massTotal: massTotal,
        hue: Math.round(Math.random() * 360),
        type: type,
        lastHeartbeat: new Date().getTime(),
        target: {
            x: 0,
            y: 0
        }
    };

    socket.on('close',function() {
        if (util.findIndex(users, currentPlayer.id) > -1)
            users.splice(util.findIndex(users, currentPlayer.id), 1);
        console.log('[INFO] User ' + currentPlayer.name + ' disconnected!');

        wss.broadcast(msgpack.encode(['playerDisconnect',{name:currentPlayer.name}]));
        bridgeChat({type: "leave", name: currentPlayer.name, hue: currentPlayer.hue});
    });

socket.on('message',function(rawdata) {
var data = msgpack.decode(rawdata);
switch(data[0]) {
    case 'gotit':
	var player = data[1];
	player.name = player.name.substr(0,25);
        console.log('[INFO] Player ' + player.name + ' connecting!');

        if (util.findIndex(users, player.id) > -1) {
            console.log('[INFO] Player ID is already connected, kicking.');
            socket.terminate();
        } else if (!util.validNick(player.name)) {
            socket.send(msgpack.encode(['kick','Invalid username.']));
            socket.terminate();
        } else {
            console.log('[INFO] Player ' + player.name + ' connected!');
            sockets[player.id] = socket;

            var radius = util.massToRadius(c.player.mass);
            var position = c.player.uniform_disposition ? util.uniformPosition(users,radius,{width:c.game.width,height:c.game.height}) : util.randomPosition(radius,{width:c.game.width,height:c.game.height});

            player.x = position.x;
            player.y = position.y;
            player.target.x = 0;
            player.target.y = 0;
            if(type === 'player') {
                player.cells = [{
                    mass: c.player.mass,
                    x: position.x,
                    y: position.y,
                    radius: radius
                }];
                player.massTotal = c.player.mass;
            }
            else {
                 player.cells = [];
                 player.massTotal = 0;
            }
            player.hue = Math.round(Math.random() * 360);
            currentPlayer = player;
            currentPlayer.lastHeartbeat = new Date().getTime();
            users.push(currentPlayer);

            wss.broadcast(msgpack.encode(['playerJoin',{name: currentPlayer.name}]));
            bridgeChat({type: "join", name: currentPlayer.name, hue: currentPlayer.hue});

            socket.send(msgpack.encode(['gameSetup',{
                gameWidth: c.game.width,
                gameHeight: c.game.height,
                virus:{
        	    	fill: c.virus.style.fill,
        	    	stroke: c.virus.style.stroke,
        	    	strokeWidth: c.virus.style.stroke_width
        		},
        		mothercell:{
        			fill: c.mothercell.style.fill,
        			stroke: c.mothercell.style.stroke,
        			strokeWidth: c.mothercell.style.stroke_width
        		}
            }]));
            console.log('Total players: ' + users.length);
        }
    break;

    case 'pingcheck':
        socket.send(msgpack.encode(['pongcheck']));
    break;

    case 'windowResized':
        currentPlayer.screenWidth = data[1].screenWidth;
        currentPlayer.screenHeight = data[1].screenHeight;
    break;

    case 'respawn':
        if (util.findIndex(users, currentPlayer.id) > -1)
            users.splice(util.findIndex(users, currentPlayer.id), 1);
        socket.send(msgpack.encode(['welcome',currentPlayer]));
        console.log('[INFO] User ' + currentPlayer.name + ' respawned!');
        bridgeChat({type: "respawn", name: currentPlayer.name, hue: currentPlayer.hue});
    break;

    case 'playerChat':
        if (c.logging.chat) {
            console.log('[CHAT] [' + (new Date()).getHours() + ':' + (new Date()).getMinutes() + '] ' + currentPlayer.name + ': ' + data[1]);
        }
        wss.broadcast(msgpack.encode(['serverSendPlayerChat',{sender: currentPlayer.name, hue: currentPlayer.hue, message: data[1]}]));
        bridgeChat({type: "msg", name: currentPlayer.name, hue: currentPlayer.hue, message: data[1]});
    break;

    case 'pass':
        if (data[1][0] === c.game.admin_pass) {
            console.log('[ADMIN] ' + currentPlayer.name + ' just logged in as an admin!');
            socket.send(msgpack.encode(['serverMSG', 'Welcome back ' + currentPlayer.name]));
            wss.broadcast(msgpack.encode(['serverMSG', currentPlayer.name + ' just logged in as admin!']));
            currentPlayer.admin = true;
            bridgeChat({type: "admin", name: currentPlayer.name, hue: currentPlayer.hue});
        } else {
            
            // TODO: Actually log incorrect passwords.
              console.log('[ADMIN] ' + currentPlayer.name + ' attempted to log in with incorrect password.');
              socket.send(msgpack.encode(['serverMSG', 'Password incorrect, attempt logged.']));
        }
    break;

    case 'whisper':
        var userFound = false;
        if(data[1].length > 1) {
            for(var e = 0; e < users.length; e++) {
                if(currentPlayer.name === data[1][0]) {
                    socket.send(msgpack.encode(['serverMSG','You cannot whisper to yourself.']));
                    return;
                }
                if(users[e].name === data[1][0]) {
                    userFound = true;
                    var message = "";
                    for(var f = 1; f < data.length; f++)
                        message = message + data[1][f] + " ";
                    sockets[users[e].id].send(msgpack.encode(['serverSendPlayerChat',{sender: currentPlayer.name, hue: currentPlayer.hue, message: message, prefix: 'WHISPER <-'}]));
                    socket.send(msgpack.encode(['serverSendPlayerChat',{sender: currentPlayer.name, hue: currentPlayer.hue, message: message, prefix: 'WHISPER ->'}]));
                }
            } 
            if(!userFound) {
                socket.send(msgpack.encode(['serverMSG','This user is not online.']));
            }
        } else {
            socket.send(msgpack.encode(['serverMSG','Please enter a name and a message.']));
        }
    break;

    case 'kick':
        if (currentPlayer.admin) {
            var reason = '';
            var worked = false;
            for (var g = 0; g < users.length; g++) {
                if (users[g].name === data[1][0] && !users[g].admin && !worked) {
                    if (data[1].length > 1) {
                        for (var h = 1; h < data[1].length; h++) {
                            if (h === data[1].length) {
                                reason = reason + data[1][h];
                            }
                            else {
                                reason = reason + data[1][h] + ' ';
                            }
                        }
                    }
                    if (reason !== '') {
                       console.log('[ADMIN] User ' + users[g].name + ' kicked successfully by ' + currentPlayer.name + ' for reason ' + reason);
                    }
                    else {
                       console.log('[ADMIN] User ' + users[g].name + ' kicked successfully by ' + currentPlayer.name);
                    }
                    socket.send(msgpack.encode(['serverMSG','User ' + users[g].name + ' was kicked by ' + currentPlayer.name]));
                    bridgeChat({type: "kick", who: {name: users[g].name, hue: users[g].hue}, by: {name: currentPlayer.name, hue: currentPlayer.hue}, reason: reason});
                    sockets[users[g].id].send(msgpack.encode(['kick',reason]));
                    sockets[users[g].id].terminate();
                    users.splice(g, 1);
                    worked = true;
                }
            }
            if (!worked) {
                socket.send(msgpack.encode(['serverMSG','Could not locate user or user is an admin.']));
            }
        } else {
            console.log('[ADMIN] ' + currentPlayer.name + ' is trying to use /kick but isn\'t an admin.');
            socket.send(msgpack.encode(['serverMSG','You are not permitted to use this command.']));
        }
    break;

    // Heartbeat function, update everytime.
    case '0':
        currentPlayer.lastHeartbeat = new Date().getTime();
        if(data[1].x !== currentPlayer.x || data[1].y !== currentPlayer.y) {
            currentPlayer.target = data[1];
        }
    break;

    case '1':
        // Fire food.
        for(var i=0; i<currentPlayer.cells.length; i++)
        {
            if(((currentPlayer.cells[i].mass >= c.player.mass + c.player.fire_food) && c.player.fire_food > 0) || (currentPlayer.cells[i].mass >= 20 && c.player.fire_food === 0)){
                var masa = (c.player.fire_food > 0) ? c.player.fire_food : currentPlayer.cells[i].mass*0.1;
                currentPlayer.cells[i].mass -= masa;
                currentPlayer.massTotal -=masa;
                massFood.push({
                    id: currentPlayer.id,
                    num: i,
                    masa: masa,
                    hue: currentPlayer.hue,
                    target: {
                        x: currentPlayer.x - currentPlayer.cells[i].x + currentPlayer.target.x,
                        y: currentPlayer.y - currentPlayer.cells[i].y + currentPlayer.target.y
                    },
                    x: currentPlayer.cells[i].x,
                    y: currentPlayer.cells[i].y,
                    radius: util.massToRadius(masa),
                    speed: 25
                });
            }
        }
    break;

    case '2':
        function splitCell(cell) {
            if(cell && cell.mass && cell.mass >= c.player.mass*2) {
                cell.mass = cell.mass/2;
                cell.radius = util.massToRadius(cell.mass);
                currentPlayer.cells.push({
                    mass: cell.mass,
                    x: cell.x,
                    y: cell.y,
                    radius: cell.radius,
                    speed: 25
                });
            }
        }

        //Split all cells
        if(currentPlayer.cells.length < c.player.split_limit && currentPlayer.massTotal >= c.player.mass*2) {
            var numMax = currentPlayer.cells.length;
            for(var d=0; d<numMax; d++) {
                splitCell(currentPlayer.cells[d]);
            }
        }
        currentPlayer.lastSplit = new Date().getTime();
    break;
}
});
});

function tickPlayer(currentPlayer) {
    if(currentPlayer.lastHeartbeat < new Date().getTime() - c.magic.max_heartbeat_interval) {
        sockets[currentPlayer.id].send(msgpack.encode(['kick','Last heartbeat received over ' + c.magic.max_heartbeat_interval + 'ms ago.']));
        sockets[currentPlayer.id].terminate();
    }

    movePlayer(currentPlayer);

    function checkFood(f) {
        if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
            if(currentCell.mass > f.mass) {
                currentCell.mass += f.mass;
                currentPlayer.massTotal += f.mass;
                food.splice(food.indexOf(f),1);
            }
        }
    }

    function checkMothercellFood(f) {
        if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
            if(currentCell.mass > f.mass) {
                currentCell.mass += f.mass;
                currentPlayer.massTotal += f.mass;
                mothercellFood.splice(mothercellFood.indexOf(f),1);
            }
        }
    }

    function checkFoodStructSpiral(f) {
        if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
            if(currentCell.mass > f.mass) {
                currentCell.mass += f.mass;
                currentPlayer.massTotal += f.mass;
                foodStructSpiral.splice(foodStructSpiral.indexOf(f),1);
            }
        }
    }

    function checkFoodStructSpiralFood(f) {
        if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
            if(currentCell.mass > f.mass) {
                currentCell.mass += f.mass;
                currentPlayer.massTotal += f.mass;
                foodStructSpiralFood.splice(foodStructSpiralFood.indexOf(f),1);
            }
        }
    }

    function checkFoodStructDotCircle(f) {
        if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
            if(currentCell.mass > f.mass) {
                currentCell.mass += f.mass;
                currentPlayer.massTotal += f.mass;
                foodStructDotCircle.splice(foodStructDotCircle.indexOf(f),1);
            }
        }
    }
    
    function checkFoodStructDotCircleFood(f) {
	if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
	    if(currentCell.mass > f.mass) {
		currentCell.mass += f.mass;
		currentPlayer.massTotal += f.mass;
		foodStructDotCircleFood.splice(foodStructDotCircleFood.indexOf(f),1);
	    }
	}
    }
    
    function checkMassFood(m) {
        if(SAT.pointInCircle(new V(m.x, m.y), playerCircle)) {
            if(!(m.id == currentPlayer.id && m.speed > 0 && z == m.num)) {
                if(currentCell.mass > m.masa * 1.1) {
            	    currentCell.mass += m.masa;
            	    currentPlayer.massTotal += m.masa;
            	    massFood.splice(massFood.indexOf(m),1);
            	}
            }
        }
    }
    
    function checkVirusCollision(f) {
	if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
	    if(currentCell.mass > f.mass) {
    		currentCell.mass += f.mass;
    		currentPlayer.massTotal += f.mass;
    		virus.splice(virus.indexOf(f),1);
    		splitCell(currentPlayer.cells[z],c.virus.split_parts);
    		currentPlayer.lastSplit = new Date().getTime();
    	    }
        }
    }
    
    function checkMothercellCollision(f) {
	if(SAT.pointInCircle(new V(f.x, f.y), playerCircle)) {
	    if(currentCell.mass > f.mass) {
    		currentCell.mass += f.mass + f.killMass;
    		currentPlayer.massTotal += f.mass + f.killMass;
    		f.killMass = 0;
    		mothercell.splice(mothercell.indexOf(f),1);
    		splitCell(currentPlayer.cells[z],c.mothercell.split_parts);
    		currentPlayer.lastSplit = new Date().getTime();
    	    }
        }
    }

    function checkMothercell(mCell) {
        if(SAT.pointInCircle(new V(mCell.x, mCell.y), playerCircle)) {
            if(currentCell.mass < mCell.mass) {
                var collision = new SAT.Response();
                var collided = SAT.testCircleCircle(playerCircle,
                    new C(new V(mCell.x, mCell.y), mCell.radius),
                    collision);
                if (collided) {
                    collision.bUser = {
                        id: currentPlayer.id,
                        name: currentPlayer.name,
                        x: currentCell.x,
                        y: currentCell.y,
                        mass: currentCell.mass,
                        num: currentPlayer.cells.indexOf(currentCell),
                        hue: currentPlayer.hue
                    };
                    collision.aUser = {
                        id: mCell.id,
                        x: mCell.x,
                        y: mCell.y,
                        mass: mCell.mass,
                        radius: mCell.radius
                    };
                    if (collision.aUser.mass > collision.bUser.mass * 1.1  && collision.aUser.radius > Math.sqrt(Math.pow(collision.aUser.x - collision.bUser.x, 2) + Math.pow(collision.aUser.y - collision.bUser.y, 2))*1.75) {
                        console.log('[DEBUG] Killing user: ' + collision.bUser.name);
                        console.log('[DEBUG] Collision info:');
                        console.log(collision);

                        var numUser = util.findIndex(users, collision.bUser.id);
                        if (numUser > -1) {
                            if(users[numUser].cells.length > 1) {
                                users[numUser].massTotal -= collision.bUser.mass;
                                users[numUser].cells.splice(collision.bUser.num, 1);
                            } else {
                                users.splice(numUser, 1);
                                wss.broadcast(msgpack.encode(['playerSuicide',{name:collision.bUser.name}]));
                                sockets[collision.bUser.id].send(msgpack.encode(['RIP']));
                                bridgeChat({type: "suicide", name: collision.bUser.name, hue: collision.bUser.hue});
                            }
                        }
                        mCell.killMass += Math.round(collision.bUser.mass);
                        mothercellKillPlayer(mCell);
                    }
                }
            }
        }
    }

    function check(user) {
        for(var i=0; i<user.cells.length; i++) {
            if(user.cells[i].mass > 10 && user.id !== currentPlayer.id) {
                var response = new SAT.Response();
                var collided = SAT.testCircleCircle(playerCircle,
                    new C(new V(user.cells[i].x, user.cells[i].y), user.cells[i].radius),
                    response);
                if (collided) {
                    response.aUser = currentCell;
                    response.bUser = {
                        id: user.id,
                        name: user.name,
                        x: user.cells[i].x,
                        y: user.cells[i].y,
                        num: i,
                        mass: user.cells[i].mass,
                        hue: user.hue
                    };
                    playerCollisions.push(response);
                }
            }
        }
        return true;
    }
    
    function collisionCheck(collision) {
        if (collision.aUser.mass > collision.bUser.mass * 1.1  && collision.aUser.radius > Math.sqrt(Math.pow(collision.aUser.x - collision.bUser.x, 2) + Math.pow(collision.aUser.y - collision.bUser.y, 2))*1.75) {
            console.log('[DEBUG] Killing user: ' + collision.bUser.name);
            console.log('[DEBUG] Collision info:');
            console.log(collision);

            var numUser = util.findIndex(users, collision.bUser.id);
            if (numUser > -1) {
                if(users[numUser].cells.length > 1) {
                    users[numUser].massTotal -= collision.bUser.mass;
                    users[numUser].cells.splice(collision.bUser.num, 1);
                } else {
                    users.splice(numUser, 1);
                    wss.broadcast(msgpack.encode(['playerDied',{name:collision.bUser.name}]));
                    sockets[collision.bUser.id].send(msgpack.encode(['RIP']));
                    bridgeChat({type: "kill", who: {name: collision.bUser.name, hue: collision.bUser.hue}, by: {name: collision.aUser.name, hue: collision.aUser.hue}});
                }
            }
            currentPlayer.massTotal += collision.bUser.mass;
            collision.aUser.mass += collision.bUser.mass;
        }
    }

    function splitCell(cell,splitParts) {
	if(cell && cell.mass) {
	    var thisUser = util.findIndex(users,currentPlayer.id);
	    var parts = splitParts-1;
	    for(var i=0;i<splitParts-1;i++) {
		if(cell.mass < c.player.mass * parts || currentPlayer.cells.length + parts > c.player.split_limit) parts--;
	    }
	    if(parts > 1) {
                cell.mass = cell.mass/parts;
	        cell.radius = util.massToRadius(cell.mass);
	        for(var a=0;a<parts;a++) {
	    	    currentPlayer.cells.push({
	    	        mass: cell.mass,
        	        x: cell.x,
            	        y: cell.y,
            	        radius: cell.radius,
            		speed: 25
        	    });
        	}
            }
        }
    }

    for(var z=0; z<currentPlayer.cells.length; z++) {
        var currentCell = currentPlayer.cells[z];
        var playerCircle = new C(
            new V(currentCell.x, currentCell.y),
            currentCell.radius
        );

	food.forEach(checkFood);
	mothercellFood.forEach(checkMothercellFood);
	foodStructSpiral.forEach(checkFoodStructSpiral);
	foodStructSpiralFood.forEach(checkFoodStructSpiralFood);
	foodStructDotCircle.forEach(checkFoodStructDotCircle);
	foodStructDotCircleFood.forEach(checkFoodStructDotCircleFood);
	massFood.forEach(checkMassFood);
	virus.forEach(checkVirusCollision);
	mothercell.forEach(checkMothercellCollision);

        if(typeof(currentCell.speed) == "undefined")
            currentCell.speed = 6.25;
        currentCell.radius = util.massToRadius(currentCell.mass);
        playerCircle.r = currentCell.radius;

        tree.clear();
        users.forEach(tree.put);
        var playerCollisions = [];
        var otherUsers = tree.get({x:currentCell.x, y: currentCell.y, w: currentCell.radius * 2, h: currentCell.radius * 2}, check);
        playerCollisions.forEach(collisionCheck);

	mothercell.forEach(checkMothercell);
    }
}

function moveloop() {
    for (var i = 0; i < users.length; i++) {
        tickPlayer(users[i]);
    }
    for (i=0; i < massFood.length; i++) {
        if(massFood[i].speed > 0) moveMass(massFood[i]);
    }
}

function gameloop() {
    if (users.length > 0) {
        users.sort( function(a, b) { return b.massTotal - a.massTotal; });

        var topUsers = [];

        for (var i = 0; i < Math.min(10, users.length); i++) {
            if(users[i].type == 'player') {
                topUsers.push({
                    id: users[i].id,
                    hue: users[i].hue,
                    name: users[i].name,
                    mass: Math.round(users[i].massTotal)
                });
            }
        }
        if (isNaN(leaderboard) || leaderboard.length !== topUsers.length) {
            leaderboard = topUsers;
            leaderboardChanged = true;
        }
        else {
            for (i = 0; i < leaderboard.length; i++) {
                if (leaderboard[i].id !== topUsers[i].id) {
                    leaderboard = topUsers;
                    leaderboardChanged = true;
                    break;
                }
            }
        }
        for (i = 0; i < users.length; i++) {
            for(var z=0; z < users[i].cells.length; z++) {
                if (users[i].cells[z].mass * (1 - (c.magic.mass_loss_rate / 1000)) > c.player.mass && users[i].massTotal > c.magic.min_mass_loss) {
                    var massLoss = users[i].cells[z].mass * (1 - (c.magic.mass_loss_rate / 1000));
                    users[i].massTotal -= users[i].cells[z].mass - massLoss;
                    users[i].cells[z].mass = massLoss;
                }
            }
        }
    }
    balanceMass();
}

function sendUpdates() {
    users.forEach( function(u) {
        // center the view if x/y is undefined, this will happen for spectators
        u.x = u.x || c.game.width / 2;
        u.y = u.y || c.game.height / 2;

        var visibleFood  = food
	    .concat(mothercellFood)
	    .concat(foodStructSpiral)
	    .concat(foodStructSpiralFood)
	    .concat(foodStructDotCircle)
	    .concat(foodStructDotCircleFood)
            .map(function(f) {
                if ( f.x > u.x - u.screenWidth/2 - 20 &&
                    f.x < u.x + u.screenWidth/2 + 20 &&
                    f.y > u.y - u.screenHeight/2 - 20 &&
                    f.y < u.y + u.screenHeight/2 + 20) {
                    return {
                	i: f.id,
                	x: util.round(f.x,1),
                	y: util.round(f.y,1),
                	r: util.round(f.radius,2),
                	m: util.round(f.mass,2),
                	h: f.hue
                    };
                }
            })
            .filter(function(f) { return f; });

        var visibleVirus  = virus
            .map(function(f) {
                if ( f.x > u.x - u.screenWidth/2 - f.radius &&
                    f.x < u.x + u.screenWidth/2 + f.radius &&
                    f.y > u.y - u.screenHeight/2 - f.radius &&
                    f.y < u.y + u.screenHeight/2 + f.radius) {
                    return {
                	i: f.id,
                	x: util.round(f.x,1),
                	y: util.round(f.y,1),
                	r: util.round(f.radius,2),
                	m: util.round(f.mass,2)
                    };
                }
            })
            .filter(function(f) { return f; });

        var visibleMothercell  = mothercell
            .map(function(f) {
                if ( f.x > u.x - u.screenWidth/2 - f.radius &&
                    f.x < u.x + u.screenWidth/2 + f.radius &&
                    f.y > u.y - u.screenHeight/2 - f.radius &&
                    f.y < u.y + u.screenHeight/2 + f.radius) {
                    return {
                	i: f.id,
                	x: util.round(f.x,1),
                	y: util.round(f.y,1),
                	r: util.round(f.radius,2),
                	m: util.round(f.mass,2)
                    };
                }
            })
            .filter(function(f) { return f; });

        var visibleMass = massFood
            .map(function(f) {
                if ( f.x+f.radius > u.x - u.screenWidth/2 - 20 &&
                    f.x-f.radius < u.x + u.screenWidth/2 + 20 &&
                    f.y+f.radius > u.y - u.screenHeight/2 - 20 &&
                    f.y-f.radius < u.y + u.screenHeight/2 + 20) {
                    return {
                	i: f.id,
                	n: f.num,
                	m: f.masa,
                	h: f.hue,
                	t: {
                	    x:util.round(f.target.x,1),
                	    y:util.round(f.target.y,1)
                	},
                	x: util.round(f.x,1),
                	y: util.round(f.y,1),
                	r: util.round(f.radius,2),
                	s: f.speed
            	    };
                }
            })
            .filter(function(f) { return f; });

        var visibleCells  = users
            .map(function(f) {
        	var rcells = f.cells.map(function(c) {
        	    return {
        		m: Math.round(c.mass),
        		x: util.round(c.x,1),
        		y: util.round(c.y,1),
        		r: util.round(c.radius,2),
        		s: c.speed
        	    };
        	});
                for(var z=0; z<f.cells.length; z++)
                {
                    if ( f.cells[z].x+f.cells[z].radius > u.x - u.screenWidth/2 - 20 &&
                        f.cells[z].x-f.cells[z].radius < u.x + u.screenWidth/2 + 20 &&
                        f.cells[z].y+f.cells[z].radius > u.y - u.screenHeight/2 - 20 &&
                        f.cells[z].y-f.cells[z].radius < u.y + u.screenHeight/2 + 20) {
                        z = f.cells.lenth;
                        if(f.id !== u.id) {
                            return {
                                i: f.id,
                                x: util.round(f.x,1),
                                y: util.round(f.y,1),
                                c: rcells,
                                m: Math.round(f.massTotal),
                                h: f.hue,
                                n: f.name
                            };
                        } else {
                            //console.log("Nombre: " + f.name + " Es Usuario");
                            return {
                                x: util.round(f.x,1),
                                y: util.round(f.y,1),
                                c: rcells,
                                m: Math.round(f.massTotal),
                                h: f.hue,
                            };
                        }
                    }
                }
            })
            .filter(function(f) { return f; });

        sockets[u.id].send(msgpack.encode(['m',visibleCells,visibleFood,visibleMass,visibleVirus,visibleMothercell]));
        if (leaderboardChanged) {
            sockets[u.id].send(msgpack.encode(['leaderboard',{
                players: users.length,
                leaderboard: leaderboard
            }]));
        }
    });
    leaderboardChanged = false;
}

function mothercellFeed() {
    for(var i=0;i<mothercell.length;i++) {
	mothercellAddFood(mothercell[i]);
    }
}

setInterval(moveloop, 1000 / 60);
setInterval(gameloop, 1000);
setInterval(sendUpdates, 1000 / c.magic.net_update_factor);
setInterval(mothercellFeed, 60000 / c.mothercell.food_speed);

// Don't touch, IP configurations.
var ipaddress = process.env.OPENSHIFT_NODEJS_IP || process.env.IP || c.network.bind_address;
var serverport = process.env.OPENSHIFT_NODEJS_PORT || process.env.PORT || c.network.port;
app.listen( serverport, ipaddress, function() {
    console.log('[DEBUG] Listening on ' + ipaddress + ':' + serverport);
    bridgeChat({type: "reset"});
});